@fieldwangai/agentflow 0.1.71 → 0.1.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,10 @@ import {
7
7
  expandAgentflowHomePath,
8
8
  getAgentflowDataRoot,
9
9
  getAgentflowDataRootOverride,
10
+ getAgentflowSkillsRoot,
11
+ getAgentflowSkillsRootOverride,
10
12
  writeAgentflowDataRootOverride,
13
+ writeAgentflowSkillsRootOverride,
11
14
  } from "./paths.mjs";
12
15
 
13
16
  function defaultAgentflowDataRoot() {
@@ -28,6 +31,14 @@ function normalizeDataRoot(raw) {
28
31
  return dataRoot;
29
32
  }
30
33
 
34
+ function normalizeSkillsRoot(raw) {
35
+ const skillsRoot = expandAgentflowHomePath(raw);
36
+ if (skillsRoot && !path.isAbsolute(skillsRoot)) {
37
+ throw new Error("skillsRoot must be an absolute path");
38
+ }
39
+ return skillsRoot;
40
+ }
41
+
31
42
  function copyEntry(src, dest) {
32
43
  const stat = fs.lstatSync(src);
33
44
  if (stat.isSymbolicLink()) {
@@ -45,6 +56,12 @@ function copyEntry(src, dest) {
45
56
  fs.copyFileSync(src, dest);
46
57
  }
47
58
 
59
+ function copyEntryIfMissing(src, dest) {
60
+ if (fs.existsSync(dest)) return false;
61
+ copyEntry(src, dest);
62
+ return true;
63
+ }
64
+
48
65
  function migrateDataRoot(oldRoot, newRoot) {
49
66
  const from = path.resolve(oldRoot);
50
67
  const to = path.resolve(newRoot);
@@ -63,9 +80,49 @@ function migrateDataRoot(oldRoot, newRoot) {
63
80
  return { migrated: true, copied: true };
64
81
  }
65
82
 
83
+ function legacyCodexSkillsRoot() {
84
+ return path.join(os.homedir(), ".codex", "skills");
85
+ }
86
+
87
+ function migrateSkillsRoot(oldRoot, newRoot) {
88
+ const from = path.resolve(oldRoot);
89
+ const to = path.resolve(newRoot);
90
+ if (from === to) return { migrated: false, copied: false };
91
+ if (pathInside(from, to)) {
92
+ throw new Error("skillsRoot target cannot be inside the current skills root");
93
+ }
94
+ if (pathInside(to, from)) {
95
+ throw new Error("skillsRoot target cannot be a parent of the current skills root");
96
+ }
97
+ fs.mkdirSync(to, { recursive: true });
98
+ if (!fs.existsSync(from)) return { migrated: true, copied: false };
99
+ for (const name of fs.readdirSync(from)) {
100
+ copyEntry(path.join(from, name), path.join(to, name));
101
+ }
102
+ return { migrated: true, copied: true };
103
+ }
104
+
105
+ function migrateLegacyCodexSkills(targetRoot) {
106
+ const from = legacyCodexSkillsRoot();
107
+ const to = path.resolve(targetRoot);
108
+ const source = path.resolve(from);
109
+ if (source === to || !fs.existsSync(source)) return { migrated: false, copied: 0, source };
110
+ if (pathInside(source, to) || pathInside(to, source)) return { migrated: false, copied: 0, source };
111
+ fs.mkdirSync(to, { recursive: true });
112
+ let copied = 0;
113
+ for (const name of fs.readdirSync(source)) {
114
+ if (copyEntryIfMissing(path.join(source, name), path.join(to, name))) copied += 1;
115
+ }
116
+ return { migrated: copied > 0, copied, source };
117
+ }
118
+
66
119
  export function readAdminStorageConfig() {
67
120
  const envDataRoot = normalizeDataRoot(process.env.AGENTFLOW_HOME || "");
121
+ const envSkillsRoot = normalizeSkillsRoot(process.env.AGENTFLOW_SKILLS_ROOT || "");
68
122
  const configuredDataRoot = getAgentflowDataRootOverride();
123
+ const configuredSkillsRoot = getAgentflowSkillsRootOverride();
124
+ const skillsRoot = getAgentflowSkillsRoot();
125
+ const legacySkillsRoot = legacyCodexSkillsRoot();
69
126
  return {
70
127
  version: 1,
71
128
  dataRoot: getAgentflowDataRoot(),
@@ -73,21 +130,49 @@ export function readAdminStorageConfig() {
73
130
  defaultDataRoot: defaultAgentflowDataRoot(),
74
131
  envDataRoot,
75
132
  envLocked: Boolean(envDataRoot),
133
+ skillsRoot,
134
+ configuredSkillsRoot,
135
+ defaultSkillsRoot: path.join(getAgentflowDataRoot(), "skills"),
136
+ envSkillsRoot,
137
+ skillsEnvLocked: Boolean(envSkillsRoot),
138
+ legacySkillsRoot,
139
+ legacySkillsRootExists: fs.existsSync(legacySkillsRoot),
76
140
  configPath: AGENTFLOW_HOME_CONFIG_PATH,
77
141
  };
78
142
  }
79
143
 
80
144
  export function writeAdminStorageConfig(config = {}) {
81
145
  const current = readAdminStorageConfig();
82
- if (current.envLocked) {
83
- throw new Error("AGENTFLOW_HOME is set; update the environment variable instead of Admin Settings");
84
- }
85
146
  const nextRoot = normalizeDataRoot(config.dataRoot ?? "");
86
147
  if (!nextRoot) throw new Error("dataRoot is required");
87
- const migration = migrateDataRoot(current.dataRoot, nextRoot);
88
- writeAgentflowDataRootOverride(nextRoot);
148
+ let migration = { migrated: false, copied: false };
149
+ if (current.envLocked) {
150
+ if (path.resolve(nextRoot) !== path.resolve(current.dataRoot)) {
151
+ throw new Error("AGENTFLOW_HOME is set; update the environment variable instead of Admin Settings");
152
+ }
153
+ } else {
154
+ migration = migrateDataRoot(current.dataRoot, nextRoot);
155
+ writeAgentflowDataRootOverride(nextRoot);
156
+ }
157
+ const afterDataRoot = readAdminStorageConfig();
158
+ if (afterDataRoot.skillsEnvLocked) {
159
+ return {
160
+ ...afterDataRoot,
161
+ migration,
162
+ skillsMigration: { skipped: true, reason: "AGENTFLOW_SKILLS_ROOT is set" },
163
+ };
164
+ }
165
+ const nextSkillsRoot = normalizeSkillsRoot(config.skillsRoot ?? afterDataRoot.defaultSkillsRoot);
166
+ if (!nextSkillsRoot) throw new Error("skillsRoot is required");
167
+ const skillsMigration = migrateSkillsRoot(current.skillsRoot, nextSkillsRoot);
168
+ writeAgentflowSkillsRootOverride(nextSkillsRoot);
169
+ const legacySkillsMigration = config.migrateLegacySkills === false
170
+ ? { migrated: false, copied: 0, source: legacyCodexSkillsRoot(), skipped: true }
171
+ : migrateLegacyCodexSkills(nextSkillsRoot);
89
172
  return {
90
173
  ...readAdminStorageConfig(),
91
174
  migration,
175
+ skillsMigration,
176
+ legacySkillsMigration,
92
177
  };
93
178
  }
@@ -137,18 +137,26 @@ export function readComposerSkillDetail(packageRoot, workspaceRoot, keyOrName) {
137
137
  return registryReadSkillDetail(packageRoot, workspaceRoot, keyOrName);
138
138
  }
139
139
 
140
+ function skillNameFromKey(keyOrName) {
141
+ const value = String(keyOrName || "").trim();
142
+ if (!value) return "";
143
+ const idx = value.indexOf(":");
144
+ return idx >= 0 ? value.slice(idx + 1).trim() : value;
145
+ }
146
+
140
147
  export function loadResourcesForSkillKeys(skillKeys, packageRoot, workspaceRoot) {
141
148
  if (!Array.isArray(skillKeys) || skillKeys.length === 0) {
142
149
  return { skills: [], references: [], skillsHint: "", hasContext: false };
143
150
  }
144
151
  const wanted = new Set(skillKeys.map((x) => String(x || "").trim()).filter(Boolean));
145
152
  if (wanted.size === 0) return { skills: [], references: [], skillsHint: "", hasContext: false };
153
+ const wantedNames = new Set(Array.from(wanted).map(skillNameFromKey).filter(Boolean));
146
154
 
147
155
  const exactByKey = new Map(registryListSkills(packageRoot, workspaceRoot).map((item) => [item.key, item]));
148
156
  const candidateItems = [];
149
157
  const seenKeys = new Set();
150
158
  for (const item of registryListUniqueSkills(packageRoot, workspaceRoot)) {
151
- if (!wanted.has(item.key) && !wanted.has(item.name)) continue;
159
+ if (!wanted.has(item.key) && !wanted.has(item.name) && !wantedNames.has(item.name)) continue;
152
160
  candidateItems.push(item);
153
161
  seenKeys.add(item.key);
154
162
  }
package/bin/lib/paths.mjs CHANGED
@@ -49,6 +49,11 @@ export function getAgentflowDataRootOverride() {
49
49
  return expandAgentflowHomePath(config.dataRoot || "");
50
50
  }
51
51
 
52
+ export function getAgentflowSkillsRootOverride() {
53
+ const config = readAgentflowHomeConfig();
54
+ return expandAgentflowHomePath(config.skillsRoot || "");
55
+ }
56
+
52
57
  export function writeAgentflowDataRootOverride(dataRoot) {
53
58
  const nextRoot = expandAgentflowHomePath(dataRoot);
54
59
  if (nextRoot && !path.isAbsolute(nextRoot)) {
@@ -62,6 +67,19 @@ export function writeAgentflowDataRootOverride(dataRoot) {
62
67
  return nextRoot;
63
68
  }
64
69
 
70
+ export function writeAgentflowSkillsRootOverride(skillsRoot) {
71
+ const nextRoot = expandAgentflowHomePath(skillsRoot);
72
+ if (nextRoot && !path.isAbsolute(nextRoot)) {
73
+ throw new Error("skillsRoot must be an absolute path");
74
+ }
75
+ const current = readAgentflowHomeConfig();
76
+ const next = { ...current, skillsRoot: nextRoot };
77
+ if (!nextRoot) delete next.skillsRoot;
78
+ fs.mkdirSync(path.dirname(AGENTFLOW_HOME_CONFIG_PATH), { recursive: true });
79
+ fs.writeFileSync(AGENTFLOW_HOME_CONFIG_PATH, JSON.stringify(next, null, 2) + "\n", "utf-8");
80
+ return nextRoot;
81
+ }
82
+
65
83
  export function getAgentflowDataRoot() {
66
84
  const env = process.env.AGENTFLOW_HOME;
67
85
  if (env != null && String(env).trim() !== "") {
@@ -72,6 +90,16 @@ export function getAgentflowDataRoot() {
72
90
  return path.join(os.homedir(), "agentflow");
73
91
  }
74
92
 
93
+ export function getAgentflowSkillsRoot() {
94
+ const env = process.env.AGENTFLOW_SKILLS_ROOT;
95
+ if (env != null && String(env).trim() !== "") {
96
+ return expandAgentflowHomePath(env);
97
+ }
98
+ const configured = getAgentflowSkillsRootOverride();
99
+ if (configured) return configured;
100
+ return path.join(getAgentflowDataRoot(), "skills");
101
+ }
102
+
75
103
  export const AGENTFLOW_DEFAULT_USER_ID = "";
76
104
 
77
105
  export function sanitizeAgentflowUserId(userId) {
@@ -2,6 +2,7 @@ import fs from "fs";
2
2
  import os from "os";
3
3
  import path from "path";
4
4
  import yaml from "js-yaml";
5
+ import { getAgentflowSkillsRoot } from "./paths.mjs";
5
6
 
6
7
  const fileCache = new Map();
7
8
  const CACHE_TTL_MS = 60_000;
@@ -9,10 +10,11 @@ const SOURCE_PRIORITY = new Map([
9
10
  ["workspace-agents", 100],
10
11
  ["workspace-codex", 95],
11
12
  ["workspace-cursor", 90],
13
+ ["agentflow-root", 85],
12
14
  ["builtin", 80],
13
- ["global-agents", 70],
14
- ["global-codex", 65],
15
- ["global-cursor", 60],
15
+ ["global-agents", 55],
16
+ ["global-codex", 50],
17
+ ["global-cursor", 45],
16
18
  ]);
17
19
 
18
20
  function readFileCached(absPath) {
@@ -28,6 +30,10 @@ function readFileCached(absPath) {
28
30
  }
29
31
  }
30
32
 
33
+ export function clearSkillRegistryCache() {
34
+ fileCache.clear();
35
+ }
36
+
31
37
  export function stripSkillFrontmatter(content) {
32
38
  const raw = String(content || "");
33
39
  const match = raw.match(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/);
@@ -109,11 +115,17 @@ export function defaultSkillSources(packageRoot, workspaceRoot) {
109
115
  },
110
116
  ];
111
117
  if (workspaceRoot) sources.push(...workspaceSkillSources(workspaceRoot, "workspace", "工作区"));
118
+ sources.push({
119
+ source: "agentflow-root",
120
+ sourceLabel: "AgentFlow Skills",
121
+ dir: getAgentflowSkillsRoot(),
122
+ installedBy: "agentflow-admin",
123
+ });
112
124
  const home = os.homedir();
113
125
  sources.push(
114
- { source: "global-agents", sourceLabel: "全局 .agents", dir: path.join(home, ".agents", "skills"), installedBy: "global" },
115
- { source: "global-cursor", sourceLabel: "全局 .cursor", dir: path.join(home, ".cursor", "skills"), installedBy: "global" },
116
- { source: "global-codex", sourceLabel: "全局 .codex", dir: path.join(home, ".codex", "skills"), installedBy: "skillhub" },
126
+ { source: "global-agents", sourceLabel: "Legacy ~/.agents", dir: path.join(home, ".agents", "skills"), installedBy: "global" },
127
+ { source: "global-cursor", sourceLabel: "Legacy ~/.cursor", dir: path.join(home, ".cursor", "skills"), installedBy: "global" },
128
+ { source: "global-codex", sourceLabel: "Legacy ~/.codex", dir: path.join(home, ".codex", "skills"), installedBy: "skillhub-legacy" },
117
129
  );
118
130
  return sources;
119
131
  }
@@ -183,9 +195,10 @@ export function listUniqueSkills(packageRoot, workspaceRoot, opts = {}) {
183
195
  export function readSkillDetail(packageRoot, workspaceRoot, keyOrName) {
184
196
  const wanted = String(keyOrName || "").trim();
185
197
  if (!wanted) return null;
198
+ const wantedName = wanted.includes(":") ? wanted.slice(wanted.indexOf(":") + 1).trim() : wanted;
186
199
  const all = listSkills(packageRoot, workspaceRoot);
187
200
  const item = all.find((skill) => skill.key === wanted)
188
- || dedupeSkillsByName(all).find((skill) => skill.name === wanted);
201
+ || dedupeSkillsByName(all).find((skill) => skill.name === wanted || skill.name === wantedName);
189
202
  if (!item) return null;
190
203
  return item;
191
204
  }